--[[
  机器人调度服务
  主要处理游戏服发来的调度请求
]]

local skynet = require "skynet"
local filename = "schedulemgr.lua"
local filelog = require "filelog"
local tabletool = require "tabletool"
local timetool = require "timetool"
require "skynet.manager"
require "pubenum"

--初始化随机数序列
math.randomseed( os.time())
math.random()

local ScheduleMgr = {
    CMD = {},
    handler = {},
    robot_list = {},    --游戏中的机器人列表
    match_list = {},    --匹配中的机器人列表
    schedule_list = {}, --调度列表
    schedulers = {},    --调度器列表
}

--调度任务类型
local ESTaskType = {
    ENTER_TABLE = 1,    --进桌
    LEAVE_TABLE = 2,    --离桌
    ENTER_MATCH = 3,    --进入匹配
}

--调度限制时间,15s
local schedule_time_limit = 15

--机器人运行状态
local Robot_runstate = false

---------------

--[Comment]
-- 生成一个调度任务
--
function ScheduleMgr.handler.gen_schedule_task(config, num, task_type)
    local now = timetool.get_time()
    local schedule_id = skynet.call(".nodeuuid", "lua", "getuuid") or math.random(1000000,9999999)
    local schedule_task = {
        id = schedule_id,   --调度id
        config = config,    --调度配置
        rids = {},          --机器人rid列表 key为rid value为是否成功 0失败 1成功
        target_num = num,   --目标数量
        callback_num = 0,   --已回调数量
        task_type = task_type,--任务类型
        start_time = now,   --开始时间
    }
    ScheduleMgr.schedule_list[schedule_id] = schedule_task

    return schedule_id
end

--[Comment]
-- 更新机器人列表
--
function ScheduleMgr.handler.update_robot_list(optype, rid, gamenode, tabletype)
    local robot_list = ScheduleMgr.robot_list
    local match_list = ScheduleMgr.match_list
    
    if optype == ESTaskType.ENTER_TABLE then
        robot_list[gamenode] = robot_list[gamenode] or {}
        robot_list[gamenode][tabletype] = robot_list[gamenode][tabletype] or {}
        local cur_list = robot_list[gamenode][tabletype]
        cur_list[rid] = 1
        --从匹配列表清除
        match_list[gamenode] = match_list[gamenode] or {}
        match_list[gamenode][tabletype] = match_list[gamenode][tabletype] or {}
        local cur_mlist = match_list[gamenode][tabletype]
        cur_mlist[rid] = nil
    elseif optype == ESTaskType.LEAVE_TABLE then
        local is_ok = false
        for _,games in pairs(robot_list) do
            for _,rids in pairs(games) do
                if rids[rid] then
                    rids[rid] = nil
                    is_ok = true
                    break
                end
            end
            if is_ok then break end
        end
        --从匹配列表清除
        if not is_ok then
            for _,games in pairs(match_list) do
                for _,rids in pairs(games) do
                    if rids[rid] then
                        rids[rid] = nil
                        is_ok = true
                        break
                    end
                end
                if is_ok then break end
            end
        end
    elseif optype == ESTaskType.ENTER_MATCH then
        match_list[gamenode] = match_list[gamenode] or {}
        match_list[gamenode][tabletype] = match_list[gamenode][tabletype] or {}
        local cur_mlist = match_list[gamenode][tabletype]
        cur_mlist[rid] = 1
        --从游戏列表删除
        robot_list[gamenode] = robot_list[gamenode] or {}
        robot_list[gamenode][tabletype] = robot_list[gamenode][tabletype] or {}
        local cur_list = robot_list[gamenode][tabletype]
        cur_list[rid] = nil
    end
end

--[Comment]
-- 重新调度
-- 理论上不应该调度失败
--
function ScheduleMgr.handler.restart_schedule_task(schedule_task, failed_list)
    local id = schedule_task.id
    local conf = schedule_task.config
    if schedule_task.task_type == ESTaskType.ENTER_TABLE then
        ScheduleMgr.CMD.request_robot_enter(conf.gamename,conf.table_index,#failed_list,conf.robotconf,id)
    elseif schedule_task.task_type == ESTaskType.LEAVE_TABLE then
        ScheduleMgr.CMD.request_robot_leave(conf.gamename,conf.table_index,failed_list,id)
    elseif schedule_task.task_type == ESTaskType.ENTER_MATCH then
        ScheduleMgr.CMD.request_robot_match(conf.gamename,conf.match_type,conf.match_level,#failed_list,conf.robotconf,id)
    end
    ScheduleMgr.schedule_list[id] = nil --只重复调度一次
end

-----------------------------

--[Comment]
-- 请求机器人进桌
-- gamename 游戏名,形如dnnode mjxznode
-- table_index 桌子索引
-- robotnum 机器人数量
-- robotconf 机器人配置,包括：diamond,coin等
--
function ScheduleMgr.CMD.request_robot_enter(gamename, table_index, robotnum, robotconf, isrestart)
    robotconf = robotconf or {}
    local schedule_id = isrestart
    if not isrestart then
        local config = { gamename = gamename, table_index = table_index, robotconf = tabletool.deepcopy(robotconf)}
        schedule_id = ScheduleMgr.handler.gen_schedule_task(config, robotnum, ESTaskType.ENTER_TABLE)
    end
    local robotlist = skynet.call(".robotmgr", "lua", "apply_robots", robotnum, robotconf)
    return skynet.call(".robotmgr", "lua", "do_robot_entertable", gamename, table_index, robotlist, schedule_id)
end

--[Comment]
-- 请求机器人离桌
-- gamename 游戏名,形如dnnode mjxznode
-- table_index 桌子索引
-- robot_rids 离桌机器人rid列表
--
function ScheduleMgr.CMD.request_robot_leave(gamename, table_index, robot_rids, isrestart)
    robot_rids = robot_rids or {}
    local schedule_id = isrestart
    if not isrestart then
        local config = { gamename = gamename, table_index = table_index, robot_rids = tabletool.deepcopy(robot_rids)}
        schedule_id = ScheduleMgr.handler.gen_schedule_task(config, #robot_rids, ESTaskType.LEAVE_TABLE)
    end
    return skynet.call(".robotmgr", "lua", "do_robot_leavetable", gamename, table_index, robot_rids, schedule_id)
end

--[Comment]
-- 请求机器人进匹配场
-- gamename 游戏名,形如dnnode mjxznode
-- match_type 匹配场类型 diamond 或 coin
-- match_level 匹配场级别 1初级 2中级 3高级
-- robotnum 机器人数量
-- robotconf 机器人配置,包括：diamond,coin等
--
function ScheduleMgr.CMD.request_robot_match(gamename, match_type, match_level, robotnum, robotconf, isrestart)
    robotconf = robotconf or {}
    local schedule_id = isrestart
    if not isrestart then
        local config = { gamename = gamename, match_type = match_type, match_level = match_level, robotconf = tabletool.deepcopy(robotconf)}
        schedule_id = ScheduleMgr.handler.gen_schedule_task(config, robotnum, ESTaskType.ENTER_MATCH)
    end
    local robotlist = skynet.call(".robotmgr", "lua", "apply_robots",robotnum, robotconf)
    return skynet.call(".robotmgr", "lua", "do_robot_match", gamename, match_type, match_level, robotlist, schedule_id)
end

--[Comment]
-- 更新调度任务，由agent发来
--
function ScheduleMgr.CMD.update_schedule_task(schedule_id, rid, gamenode, tabletype, is_entermatch)
    schedule_id = schedule_id or 0
    local schedule_task = ScheduleMgr.schedule_list[schedule_id]
    if schedule_task then
        schedule_task.callback_num = schedule_task.callback_num + 1
        if schedule_task.task_type == ESTaskType.ENTER_TABLE then
            if gamenode and tabletype then  
                schedule_task.rids[rid] = 1 --进桌成功
                ScheduleMgr.handler.update_robot_list(ESTaskType.ENTER_TABLE, rid, gamenode, tabletype)
            else
                schedule_task.rids[rid] = 0 --进桌失败
            end
        elseif schedule_task.task_type == ESTaskType.LEAVE_TABLE then
            if gamenode and tabletype then
                schedule_task.rids[rid] = 0 --离桌失败
            else
                schedule_task.rids[rid] = 1 --离桌成功
                ScheduleMgr.handler.update_robot_list(ESTaskType.LEAVE_TABLE, rid)
            end
        elseif schedule_task.task_type == ESTaskType.ENTER_MATCH then
            if gamenode and tabletype then
                schedule_task.rids[rid] = 1 --进入匹配成功
                ScheduleMgr.handler.update_robot_list(ESTaskType.ENTER_MATCH, rid, gamenode, tabletype)
            else
                schedule_task.rids[rid] = 0 --进入匹配失败
            end
        end

        --全部回调后，处理失败的回调
        if schedule_task.callback_num >= schedule_task.target_num then
            local failed_list = {}
            for rid,status in pairs(schedule_task.rids) do
                if status == 0 then
                    table.insert(failed_list,rid)
                end
            end
            if #failed_list > 0 then
                local now = timetool.get_time()
                if (now - schedule_task.start_time) < schedule_time_limit then
                    ScheduleMgr.handler.restart_schedule_task(schedule_task, failed_list)
                else
                    filelog.sys_error("ScheduleMgr.CMD.update_schedule_task: schedule task do failed! ", schedule_task)
                    ScheduleMgr.schedule_list[schedule_id] = nil
                end
            else
                ScheduleMgr.schedule_list[schedule_id] = nil
            end
        end
    elseif gamenode and tabletype then
        if is_entermatch then   --进入匹配场
            ScheduleMgr.handler.update_robot_list(ESTaskType.ENTER_MATCH, rid, gamenode, tabletype)
        else                    --进桌
            ScheduleMgr.handler.update_robot_list(ESTaskType.ENTER_TABLE, rid, gamenode, tabletype)
        end
    else    --离桌
        ScheduleMgr.handler.update_robot_list(ESTaskType.LEAVE_TABLE, rid)
    end

    return true
end

--[Comment]
-- 获得机器人信息(每个游戏有多少机器人)
--
function ScheduleMgr.CMD.get_robot_info()
    local msg = {
        ingame = {},    --游戏中
        inmatch = {},   --匹配中
    }

    local robot_list = ScheduleMgr.robot_list
    for gamename,games in pairs(robot_list) do
        msg.ingame[gamename] = msg.ingame[gamename] or {}
        for tabletype,rids in pairs(games) do
            msg.ingame[gamename][tabletype] = 0
            for k,v in pairs(rids) do
                 msg.ingame[gamename][tabletype] =  msg.ingame[gamename][tabletype] + 1
            end
        end
    end

    local match_list = ScheduleMgr.match_list
    for gamename,games in pairs(match_list) do
        msg.inmatch[gamename] = msg.inmatch[gamename] or {}
        for tabletype,rids in pairs(games) do
            msg.inmatch[gamename][tabletype] = 0
            for k,v in pairs(rids) do
                 msg.inmatch[gamename][tabletype] =  msg.inmatch[gamename][tabletype] + 1
            end
        end
    end
    
    return msg
end

--[Comment]
-- 获得游戏机器人
-- 返回机器人的rid列表
-- gamename 游戏名，形如 mjxznode
-- sessiontype 场次类型 diamond金豆场 coin金币场
--
function ScheduleMgr.CMD.get_game_robots(gamename, sessiontype)
    if ScheduleMgr.robot_list[gamename] 
    and ScheduleMgr.robot_list[gamename][sessiontype] then
        local rids = ScheduleMgr.robot_list[gamename][sessiontype]
        return tabletool.deepcopy(rids) or {}
    end
    
    return {}
end

--[Comment]
-- 改变机器人状态 true运行 false关闭
--
function ScheduleMgr.CMD.change_robot_state(newstate, gamename)
    --只调度某个游戏
    if gamename then
        if newstate and not Robot_runstate then
            Robot_runstate = newstate
            ScheduleMgr.match_list = {}
            ScheduleMgr.robot_list = {}
            ScheduleMgr.schedule_list = {}
        elseif not newstate then
            skynet.call(".robotmgr", "lua", "remove_allrobots", gamename)
        end

        for _,name in pairs(ScheduleMgr.schedulers) do
            if name == gamename then
                local svrname = name .. "scheduler"
                skynet.send("." .. svrname , "lua", "change_schedule_state", newstate)
            end
        end
        return
    end

    if not Robot_runstate and newstate then
        ScheduleMgr.match_list = {}
        ScheduleMgr.robot_list = {}
        ScheduleMgr.schedule_list = {}
    elseif not newstate then
        --所有机器人离桌
        skynet.call(".robotmgr", "lua", "remove_allrobots")
    end
    Robot_runstate = newstate

    --改变所有调度器状态
    for _,name in pairs(ScheduleMgr.schedulers) do
        local svrname = name .. "scheduler"
        skynet.send("." .. svrname , "lua", "change_schedule_state", newstate)
    end
end

--[Comment]
-- 获得机器人状态 true运行 false关闭
--
function ScheduleMgr.CMD.get_robot_state()
    return Robot_runstate
end

--初始化，启动游戏调度服务
function ScheduleMgr.init()
    --所有调度服务
    --ScheduleMgr.schedulers = { "dn", "psz", "pdk", "ddz"}
	ScheduleMgr.schedulers = { }

    --启动调度服务
    for _,name in pairs(ScheduleMgr.schedulers) do
        local svrname = name .. "scheduler"
        skynet.name("." .. svrname , skynet.uniqueservice("schedulelogic/" .. svrname ) )
    end
end

-----------

skynet.start(function()
    skynet.dispatch("lua",function(session, source, cmd, ...)
        if Robot_runstate or string.find(cmd,"request") == nil then
            local f = ScheduleMgr.CMD[cmd]
            if f then
                skynet.retpack(f(...))
            else
                skynet.retpack()
            end
        else
            skynet.retpack()
        end
    end)
    ScheduleMgr.init()
end)